/////////////////////////////////////////////////////////////////////////////
//  Copyright(c)2008, Qizhi Zhang. http://www.moorwind.com. 
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
/////////////////////////////////////////////////////////////////////////////
package net.oauth
{
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.net.URLRequestMethod;
	import flash.utils.ByteArray;
	
	import net.oauth.encryption.Base64;
	import net.oauth.encryption.HMAC;
	import net.oauth.encryption.Hex;
	import net.oauth.encryption.SHA1;
	
	/**
	 * The OAuth protocol enables websites or applications (Consumers) to access Protected Resources 
	 * from a web service (Service Provider) via an API, without requiring Users to disclose 
	 * their Service Provider credentials to the Consumers. More generally, 
	 * OAuth creates a freely-implementable and generic methodology for API authentication. 
	 * 
	 * References
	 * <br/>[NIST] National Institute of Standards and Technolog, NIST., “NIST Brief Comments on Recent Cryptanalytic Attacks on Secure Hashing Functions and the Continued Security Provided by SHA-1.” 
	 * <br/>[RFC2045] Freed, N. and N. Borenstein, “Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies,” RFC 2045. 
	 * <br/>[RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, “HMAC: Keyed-Hashing for Message Authentication,” RFC 2104. 
	 * <br/>[RFC2119] Bradner, B., “Key words for use in RFCs to Indicate Requirement Levels,” RFC 2119. 
	 * <br/>[RFC2606] Eastlake, D. and A. Panitz, “Reserved Top Level DNS Names,” RFC 2606. 
	 * <br/>[RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P., and T. Berners-Lee, “Hypertext Transfer Protocol – HTTP/1.1,” RFC 2616. 
	 * <br/>[RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A., and L. Stewart, “HTTP Authentication: Basic and Digest Access Authentication,” RFC 2617. 
	 * <br/>[RFC3447] Jonsson, J. and B. Kaliski, “Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography; Specifications Version 2.1,” RFC 3447. 
	 * <br/>[RFC3629] Yergeau, F., “UTF-8, a transformation format of Unicode and ISO 10646,” RFC 3629. 
	 * <br/>[RFC3986] Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” RFC 3986. 
	 * <br/>[SHA1] De Canniere, C. and C. Rechberger, “Finding SHA-1 Characteristics: General Results and Applications.” 
	 * <br/>
	 * <br/><br/>OAuth协议致力于使网站和应用程序（统称为消费方）能够在无须用户透露其认证证书的情况下，
	 * 通过API访问某个web服务（统称为服务提供方）的受保护资源。更一般地说，OAuth为API认证提供了一个可自由实现且通用的方法。
	 * 
	 * @see http://oauth.net/core/1.0/
	 * @author Qizhi Zhang
	 */
	public class OAuth
	{
		/**
		 * OAuth authentication is the process in which Users grant access to their Protected Resources 
		 * without sharing their credentials with the Consumer. 
		 * OAuth uses Tokens generated by the Service Provider instead of the User’s credentials in 
		 * Protected Resources requests. 
		 *
		 * @param oauth_consumer_key The Consumer Key
		 * @param oauth_consumer_secret_key The Consumer Secret
		 * 
		 * @see http://oauth.net/core/1.0/#auth_step1
		 * @langversion ActionScript 3.0
		 * @playerversion Flash 8.5
		 * @tiptext
		 */
		public function OAuth(oauth_consumer_key:String, oauth_consumer_secret_key:String, oauth_version:String = "1.0")
		{
			this._oauth_consumer_key = oauth_consumer_key;
			this._oauth_consumer_secret_key = oauth_consumer_secret_key;
			this._oauth_version = oauth_version;
		}
		
		/**
		 * A value used by the Consumer to identify itself to the Service Provider. 
		 * <br/><br/>消费方用来向服务提供方标示身份的值。
		 */
		private var _oauth_consumer_key:String;
		public function get oauth_consumer_key():String
		{
			return this._oauth_consumer_key;
		}
		public function set oauth_consumer_key(value:String):void
		{
			_oauth_consumer_key = value;
		}
		
		/**
		 * A secret used by the Consumer to establish ownership of the Consumer Key.
		 * <br/><br/>消费方用于建立对消费方键值所有权的密钥。 
		 */
		private var _oauth_consumer_secret_key:String;
		public function get oauth_consumer_secret_key():String
		{
			return this._oauth_consumer_secret_key;
		}
		public function set oauth_consumer_secret_key(value:String):void
		{
			_oauth_consumer_secret_key = value;
		}
		
		/**
		 * OAuth Version. 
		 * <br/><br/>OAuth 版本号。
		 */
		private var _oauth_version:String;
		public function get oauth_version():String
		{
			return this._oauth_version;
		}
		public function set oauth_version(value:String):void
		{
			_oauth_version = value;
		}
		/**
		 * The Consumer declares a signature method in the oauth_signature_method parameter, 
		 * generates a signature, and stores it in the oauth_signature parameter. 
		 * The Service Provider verifies the signature as specified in each method. 
		 * When verifying a Consumer signature, the Service Provider SHOULD check the request nonce to 
		 * ensure it has not been used in a previous Consumer request.
		 * <br/>The signature process MUST NOT change the request parameter names or values, with the exception of the oauth_signature parameter.   
		 * <br/><br/>加密方法。
		 */
		private var _oauth_signature_method:String = "HMAC-SHA1";
		public function get oauth_signature_method():String
		{
			return _oauth_signature_method;
		}
		
		/**
		 * A set of resource URI.
		 * <br/><br/>IEndPoint提供了数据接口的方法。
		 */
		private var _endPoint:IEndPoint;
		public function get endPoint():IEndPoint
		{
			return _endPoint;
		}
		public function set endPoint(value:IEndPoint):void
		{
			_endPoint = value;
		}
		
		/**
		 * Start authenticating with OAuth.
		 * <br/>OAuth authentication is the process in which Users grant access to their Protected Resources without sharing their 
		 * credentials with the Consumer. OAuth uses Tokens generated by the Service Provider instead of the User’s credentials 
		 * in Protected Resources requests.
		 * <br/>OAuth Authentication is done in three steps: 
		 * <br/>1.The Consumer obtains an unauthorized Request Token. 
		 * <br/>2.The User authorizes the Request Token (Need Adobe AIR open a new html window for user authentication).
		 * <br/>3.The Consumer exchanges the Request Token for an Access Token. 
		 */
		public function getRequestToken(loader:URLLoader):void
		{
			if(!loader) throw new Error("Null argument error.");
			if(!endPoint) throw new Error("Cann't find end point definition.");
			tokenAccess(loader, endPoint.request_token_URL);
		}
		
		/**
		 * The Consumer exchanges the Request Token for an Access Token.
		 */
		public function getAccessToken(loader:URLLoader, authorizedRequestToken:String):void
		{
			if(!loader) throw new Error("Null argument error.");
			if(!endPoint) throw new Error("Cann't find end point definition.");
			tokenAccess(loader, endPoint.access_token_URL, authorizedRequestToken);
		}
		
		private function tokenAccess(loader:URLLoader, endpoint:String,  token:String = ""):void
		{
			if(!loader) throw new Error("Null argument error.");
			var baseURL		:String		= creatBaseURL( this.oauth_consumer_key, this.oauth_version, token );			
			var baseString	:String 	= creatBaseString( URLRequestMethod.GET, endpoint, baseURL );
			var signature	:String  	= creatSignature( this.oauth_consumer_secret_key, baseString );
			var request	 	:String  	= endpoint + "?" + baseURL + "&oauth_signature=" + signature;
						
			loader.load( new URLRequest( request ) );
		}
		
		/**
		 * access the resource.
		 */
		public function baseAccess(loader:URLLoader, call:String, endpoint:String, method:String = "GET", params:String = "", data:String = "",token:String = ""):void
		{
			if(!loader) throw new Error("Null argument error");
			var baseCall:String	= call;
			var baseURL	:String	= creatBaseURL( this.oauth_consumer_key, this.oauth_version, token );
		
			//creat baseString for signature
			var baseString:String = creatBaseString( method, (endpoint + baseCall ), baseURL, params, data );			
			var signature:String  = creatSignature( this.oauth_consumer_secret_key, baseString );
			
			//creat requestURL
			var requestURL:String = endpoint + call;			
			requestURL += "?" + baseURL + "&oauth_signature=" + signature;
			if(params && params != "") requestURL += "&" + params;
			
			//make request			
			var request:URLRequest = new URLRequest( requestURL );
			request.method = method;			
			if( method != "GET" ) request.data = data ;
										
			//trace( " [ Get Access ] " + requestURL + "\n");
			
			loader.load( request );
		
		}
		
		/**
		 * access the binary resource such as upload photos.
		 */
		public function binaryAccess(loader:URLLoader, call:String, data:ByteArray, extension:String, token:String, endpoint:String, method:String = "POST"):void
		{			
			if(!loader) throw new Error("Null argument error");
			var baseCall	:String		= call;
			var base64data	:String = Base64.encodeByteArray( data );
			var reg:RegExp = new RegExp("\n", "g");
			
			base64data = base64data.replace(reg,"");
						
			var baseURL		:String		= creatBaseURL( this.oauth_consumer_key, this.oauth_version, token );			
			var baseString:String = creatBaseString(method, (endpoint + baseCall ), baseURL);
			
			var signature:String  = creatSignature( this.oauth_consumer_secret_key, baseString );
			var requestURL:String = endpoint + call + "?" + baseURL + "&oauth_signature=" + encodeURIComponent(signature);			
			var contentDisposition:URLRequestHeader = new URLRequestHeader("Content-Disposition", "." + extension);	
			var contentType:URLRequestHeader = new URLRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
			var request:URLRequest = new URLRequest( requestURL );
			
			request.method = method;
			request.requestHeaders.push(contentDisposition);
			request.requestHeaders.push(contentType);
			request.data = base64data ;

			trace( " [ Get Access ] " + requestURL + "\n");
			
			loader.load( request );
		}
		
		/** base URL for signature creating */
		private static function creatBaseURL(oauth_consumer_key:String, oauth_version:String, token:String = ""):String
		{
			var baseURL		:String = "";
			
			baseURL += "oauth_consumer_key=" + encodeURIComponent( oauth_consumer_key );			
			baseURL += "&oauth_nonce=" + Nonce.OAuthNonce;
			baseURL += "&oauth_signature_method=HMAC-SHA1";
			baseURL += "&oauth_timestamp=" + Nonce.OAuthTimeStamp;
			baseURL += "&oauth_token=" + encodeURIComponent( token );
			baseURL += "&oauth_version=" + oauth_version;
			
			return baseURL;
		}
				
		/** creat baseString  */
		private static function creatBaseString(method:String, request:String, ...args):String
		{
			var baseString:String = method + "&";			
			baseString += encodeURIComponent( request ) + "&";
			
			var s:String = "";
			var a:Array = [];
			var i:int;
			for(i = 0; i < args.length; i++)
			{
				a = a.concat(creatQueryCollection(args[i]));
			}
			a.sort();
			
			
			for(i = 0; i < a.length; i++)
			{
				s += a[i] + "&";
			}
			
			s = s.substr(0, s.length - 1);
			
			baseString += encodeURIComponent(s);
			return baseString;
		}
		
		/** creat query string collection  */
		private static function creatQueryCollection(urlString:String):Array/* of String("Name=Value") */
		{
			if(!urlString)
				return [];
				
			var r:Array = [];
			var a:Array = urlString.split("&");
			var i:int = 0;
			
			for( i = 0; i < a.length; i++ )
			{
				if(a[i] != "") r.push(a[i]);		
			} 
			
			return r;
		}
		
		/** creat signature  */
		private static function creatSignature(oauth_consumer_secret_key:String, baseString:String):String
		{
			var keyData:ByteArray, cryptoData:ByteArray;
			var hmac:HMAC  	= new HMAC( new SHA1 );			
			keyData 		= Hex.toArray( Hex.fromString( oauth_consumer_secret_key + "&" ) );
			cryptoData  	= Hex.toArray( Hex.fromString( baseString ) );
			
			var outDate:ByteArray = hmac.compute( keyData, cryptoData );
						
			return Base64.encodeByteArray( outDate );
		}

	}
}